package com.lanhao.aot.ecdh;

import java.security.KeyFactory;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.Provider;
import java.security.PublicKey;
import java.security.Security;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.Map;

import javax.crypto.KeyAgreement;

import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.google.common.collect.Maps;

import cn.hutool.crypto.digest.MD5;

/**
 * ECDH加密工具类
 *
 * @author kunzhou3
 * @date 2020/04/24
 */
public class ECDHUtil {
	
	private static final Logger log = LoggerFactory.getLogger(ECDHUtil.class);


	private final static String PROVIDER = "BC";
	private final static String ECDH = "ECDH";

	/**
	 * 公钥
	 */
	public final static String PUBLIC_KEY = "publicKey";

	/**
	 * 私钥
	 */
	public final static String PRIVATE_KEY = "privateKey";

	/**
	 * 把字节数组转换成16进制字符串
	 *
	 * @param bArray 字节数组
	 * @return 16进制字符串
	 */

	private static String bytesToHexString(byte[] bArray) {
		StringBuilder sb = new StringBuilder(bArray.length);
		String sTemp;
		for (byte abArray : bArray) {
			sTemp = Integer.toHexString(0xFF & abArray);
			if (sTemp.length() < 2) {
				sb.append(0);
			}
			sb.append(sTemp.toUpperCase());
		}
		return sb.toString();
	}

	/**
	 * 把16进制字符串转换成字节数组
	 *
	 * @param hex 6进制字符串
	 * @return 字节数组
	 */

	private static byte[] hexStringToByte(String hex) {
		if (hex.length() % 2 != 0) {
			hex = "0" + hex;
		}
		int len = (hex.length() / 2);
		byte[] result = new byte[len];
		char[] achar = hex.toCharArray();
		for (int i = 0; i < len; i++) {
			int pos = i * 2;
			result[i] = (byte) (toByte(achar[pos]) << 4 | toByte(achar[pos + 1]));
		}
		return result;
	}

	/**
	 * 16进制字节转换
	 * 
	 * @param c 字节
	 * @return 转换结果
	 */
	private static byte toByte(char c) {
		return (byte) "0123456789ABCDEF".indexOf(c);
	}

	/**
	 * 生成客户端服务端初始化公私钥
	 * 
	 * @return 密钥对
	 */
	public static Map<String, String> generatorKey() {
		Map<String, String> keys = Maps.newHashMap();
		try {
			Provider provider = new BouncyCastleProvider();
			// 服务端初始化密钥
			KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("ECDH", provider);
			keyPairGenerator.initialize(192);
			// 生成密钥对
			KeyPair keyPair = keyPairGenerator.generateKeyPair();
			// 生成服务端公私钥
			String serPubKey = bytesToHexString(keyPair.getPublic().getEncoded());
			String serPriKey = bytesToHexString(keyPair.getPrivate().getEncoded());

			keys.put(PUBLIC_KEY, serPubKey);
			keys.put(PRIVATE_KEY, serPriKey);
		} catch (NoSuchAlgorithmException e) {
			e.printStackTrace();
		}
		return keys;
	}

	/**
	 * 秘钥磋商
	 * 
	 * @param serPriKey 服务端密钥
	 * @param cliPubKey 客户端公钥
	 * @return 共享密钥
	 */
	public static String ecdhKey(String serPriKey, String cliPubKey) {
		try {
			// 使用ECDH-BC前，BC方法添加进环境信息内
			Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider());

			// 初始化ecdh keyFactory
			KeyFactory keyFactory = KeyFactory.getInstance(ECDH, PROVIDER);
			// 处理私钥
			byte[] priKeyBytes = hexStringToByte(serPriKey);
			PKCS8EncodedKeySpec pkcs8EncodedKeySpec = new PKCS8EncodedKeySpec(priKeyBytes);
			PrivateKey ecPriKey = keyFactory.generatePrivate(pkcs8EncodedKeySpec);
			// 处理公钥
			byte[] pubKeyBytes = hexStringToByte(cliPubKey);
			X509EncodedKeySpec pubX509 = new X509EncodedKeySpec(pubKeyBytes);
			PublicKey ecPubKey = keyFactory.generatePublic(pubX509);
			// 秘钥磋商生成新的秘钥byte数组
			KeyAgreement aKeyAgree = KeyAgreement.getInstance(ECDH, PROVIDER);
			aKeyAgree.init(ecPriKey);
			aKeyAgree.doPhase(ecPubKey, true);
			return bytesToHexString(aKeyAgree.generateSecret());
		} catch (Exception e) {
			e.printStackTrace();
			return null;
		}
	}
	
	public static void main(String[] args) {
		// 1、客户端随机生成新的密钥对
		Map<String, String> clientKeyPair = ECDHUtil.generatorKey();
		String clientPublicKey = clientKeyPair.get(ECDHUtil.PUBLIC_KEY);
		String clientPrivateKey = clientKeyPair.get(ECDHUtil.PRIVATE_KEY);
		log.info("[客户端生成的公钥]clientPublicKey={}", clientPublicKey);
		log.info("[客户端生成的私钥]clientPrivateKey={}", clientPrivateKey);
		
		// 2、服务端随机生成新的密钥对
		Map<String, String> serviceKeyPair = ECDHUtil.generatorKey();
		String serverPublicKey = serviceKeyPair.get(ECDHUtil.PUBLIC_KEY);
		String serverPrivateKey = serviceKeyPair.get(ECDHUtil.PRIVATE_KEY);
		log.info("[服务端生成的公钥]serverPublicKey={}", serverPublicKey);
		log.info("[服务端生成的私钥]serverPrivateKey={}", serverPrivateKey);
		
		// 3、服务端用私钥与客户端的公钥生成共享密钥
        String serverEcdhKey = ECDHUtil.ecdhKey(serverPrivateKey, clientPublicKey);
        log.info("服务端用私钥 servicePriKey={} 与客户端的公钥 clientPublicKey={} 生成共享密钥为: serverEcdhKey={}",
        		serverPrivateKey, clientPublicKey, serverEcdhKey);
        
        // 4、客户端用私钥与服务端的公钥生成共享密钥
        String clientEcdhKey = ECDHUtil.ecdhKey(clientPrivateKey, serverPublicKey);
        log.info("客户端用私钥 clientPrivateKey={}与服务端的公钥 serverPublicKey={} 生成共享密钥为: clientEcdhKey={}",
        		clientPrivateKey, serverPublicKey, clientEcdhKey);
        
        // 5、协商结果比对
        log.info("客户端和服务端的协商密钥比对结果result={}", clientEcdhKey.equals(serverEcdhKey));
        
        // 6、将协商的密钥转换成AES的16位密钥
        String serverAESSecretKey = MD5.create().digestHex16(serverEcdhKey);
        
        // 7、打印最终客户端与服务端交互的AES密钥
        log.info("最终服务端生成的交互AES密钥={}", serverAESSecretKey);
        
        // 8、客户端生成
        String clientAESSecretKey = MD5.create().digestHex16(clientEcdhKey);
        log.info("最终客户端生成的交互AES密钥={}", clientAESSecretKey);
        
	}
}
